﻿//-------------------------------------------------------------------------------------------
// <copyright file="ViewModel.cs" company="Russell Wilkins">
//     Copyright (c) Russell Wilkins 2014. All Rights Reserved.
// </copyright>
// <author>Russell Wilkins</author>
// <license href="license.txt">
//     The use of this software is governed by the Microsoft Public License
//     which is included with this distribution.
// </license>
//-------------------------------------------------------------------------------------------
namespace Ripple.TestClient.ViewModels
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;

    /// <summary>
    /// Base class for all ViewModels
    /// </summary>
    public abstract class ViewModel<TModel> : DependencyObject, IDisposable
        where TModel : class, INotifyPropertyChanged
    {
        public static readonly DependencyProperty CanRefreshProperty = DependencyProperty.Register("CanRefresh", typeof(bool), typeof(ViewModel<TModel>), new PropertyMetadata(true));
        public static readonly DependencyProperty DisplayNameProperty = DependencyProperty.Register("DisplayName", typeof(string), typeof(ViewModel<TModel>), new PropertyMetadata("ViewModel"));
        public static readonly DependencyProperty LongDisplayNameProperty = DependencyProperty.Register("LongDisplayName", typeof(string), typeof(ViewModel<TModel>), new PropertyMetadata("ViewModel"));
        public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model", typeof(TModel), typeof(ViewModel<TModel>), new PropertyMetadata(null, new PropertyChangedCallback(ModelValueChanged)));

        private DelegateCommand refreshCommand;
        private bool refreshing;

        protected ViewModel()
        {
        }

#if DEBUG

        /// <summary>
        /// Useful for ensuring that ViewModel objects are properly garbage collected.
        /// </summary>
        ~ViewModel()
        {
            if (this.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
            {
                this.Debug();
            }
            else
            {
                Action a = () =>
                {
                    this.Debug();
                };

                this.Dispatcher.Invoke(a, null);
            }
        }

#endif

        public bool CanRefresh
        {
            get { return (bool)GetValue(CanRefreshProperty); }
            set { SetValue(DisplayNameProperty, value); }
        }

        public string DisplayName
        {
            get { return (string)GetValue(DisplayNameProperty); }
            set { SetValue(DisplayNameProperty, value); }
        }

        public string LongDisplayName
        {
            get { return (string)GetValue(LongDisplayNameProperty); }
            set { SetValue(LongDisplayNameProperty, value); }
        }

        public TModel Model
        {
            get { return (TModel)GetValue(ModelProperty); }
            set { SetValue(ModelProperty, value); }
        }

        public DelegateCommand RefreshCommand
        {
            get
            {
                if (this.refreshCommand == null)
                {
                    this.refreshCommand = new DelegateCommand(param => this.RefreshCommandExecutor());
                }

                return this.refreshCommand;
            }
        }

        public void Dispose()
        {
            this.OnDispose();
        }

        public virtual void ModelChanged()
        {
        }

        public virtual void Refresh()
        {
            this.refreshing = true;
            this.Reset();

            // TODO: need to fix the cross thread exception this causes in list view, disable threading for now.
            //Task.Factory.StartNew(() => this.InternalRefresh());
            this.Dispatcher.Invoke(() => this.InternalRefresh());
        }

        protected virtual void InternalRefresh()
        {
            this.RefreshData();
            this.refreshing = false;
        }

        protected virtual void OnDispose()
        {
        }

        protected virtual void PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (!this.refreshing)
            {
                this.Refresh();
            }
        }

        protected void RaiseEvent(EventHandler eventHandler)
        {
            EventHandler tempEvent = eventHandler;
            if (tempEvent != null)
            {
                tempEvent(this, new EventArgs());
            }
        }

        protected virtual void RefreshData()
        {
        }

        protected virtual void Reset()
        {
        }

        private static void ModelValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
        {
            ViewModel<TModel> instance = dependencyObject as ViewModel<TModel>;
            instance.Reset();
            instance.RefreshData();
            instance.ModelChanged();
        }

        [Conditional("DEBUG")]
        private void Debug()
        {
            string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
            System.Diagnostics.Debug.WriteLine(msg);
        }

        // Refresh
        private void RefreshCommandExecutor()
        {
            if (this.CanRefresh)
            {
                this.Refresh();
            }
        }
    }
}